const { generateRandomPrime, gcd, powMod, str2codes, codes2str } = require("./utils");

// 非对称加密
class RSA {
  // 进制
  base = 10;
  a; // 质数A
  b; // 质数B
  n; // n = a * b 
  phi; // 欧拉函数 phi(n) = (a-1)*(b-1)
  e; // 公钥 1 < e < phi && e 与 phi 最大公约数为1
  d; // 私钥 1 < d < phi && d * e % phi = 1
  constructor({ n, e, d } = {}) {
    if (n) {
      this.n = n;
      this.e = e;
      this.d = d;
      return
    }
    this.generateKeys();
  }

  // 生成公钥
  generateE() {
    const nums = []
    for (let e = 2; e < this.phi; e++) {
      if (gcd(e, this.phi) === 1) {
        nums.push(e);
        if (nums.length >= 100) break;
      }
    }
    return nums[Math.floor(Math.random() * nums.length)];
  }

  // 生成私钥
  generateD() {
    for (let d = this.phi - 1; d > 1; d--) {
      if (d % this.phi * this.e % this.phi === 1) return d;
    }
  }

  generateKeys() {
    this.a = generateRandomPrime(3000, 5000);
    this.b = generateRandomPrime(5000, 10000);
    this.n = this.a * this.b; //（n值越大越安全，大值计算,此处先控制在1亿以内,超过1E要换成bigint类型计算）
    this.phi = (this.a - 1) * (this.b - 1);
    this.e = this.generateE();
    this.d = this.generateD();
    return { n: this.n, e: this.e, d: this.d }
  }

  // 加密
  encode(str, e = this.e, n = this.n) {
    let bytes = str2codes(str);
    let newBytes = [];
    bytes.forEach((char) => {
      let newChar = powMod(char, e, n);
      newBytes.push(Number(newChar).toString(this.base));
    });
    return newBytes.join(',');
  }

  // 解密
  decode(str, d = this.d, n = this.n) {
    let bytes = str.split(',');
    let newBytes = [];
    bytes.forEach((char) => {
      let newChar = powMod(parseInt(char, this.base), d, n);
      newBytes.push(Number(newChar));
    });
    let plaintext = codes2str(newBytes);
    return plaintext;
  }
}


const rsa = new RSA();
console.log(rsa);
const code = rsa.encode('在JavaScript中，原生的数字类型（Number）是基于64位浮点数的，这使得它们对于大多数计算来说足够大，但对于非常大的整数或需要高精度的小数时，就可能不够用了。当涉及到大数计算，比如处理超过');
console.log("encoded:", code, code.length);
const text = rsa.decode(code);
console.log("decoded:", text, text.length);
